/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2023.
 * Description: ubi support periodic scan to avoid ecc problem
 * Author: Pang LiYuan
 * Create: 2023-11-13
 */
#include "ubi.h"

void ubi_bfcheck_traverse_peb(struct work_struct *work)
{
	int err = 0;
	struct delayed_work *dwork = container_of(work, struct delayed_work, work);
	struct ubi_device *ubi = container_of(dwork, struct ubi_device, bfcheck_work);

	if (!ubi->bfcheck_should_work) {
		ubi_msg(ubi, "force bit flip check stop.\n");
		return;
	}

	if (ubi->bfcheck_cur_peb == 0)
		ubi_msg(ubi, "start bit flip check for total %d blocks.", ubi->peb_count);

	if (likely(ubi->bfcheck_cur_peb < ubi->peb_count)) {
		err = ubi_bitflip_check(ubi, ubi->bfcheck_cur_peb, 0);
		if (err == -EUCLEAN)
			/* bit flips happened and PEB is scheduled for scrubbing */
			ubi_msg(ubi, "bit flip detected on peb:%d", ubi->bfcheck_cur_peb);

		ubi->bfcheck_cur_peb++;
		/* convert seconds to jiffies. during the checking, we sleep only few second */
		schedule_delayed_work(&ubi->bfcheck_work, (unsigned long)(ubi->bfcheck_scan_block_interval * HZ));
	} else {
		ubi->bfcheck_cur_peb = 0;
		/* still convert seconds to jiffies. we have finished scan all peb and sleep a long time */
		schedule_delayed_work(&ubi->bfcheck_work, (unsigned long)(ubi->bfcheck_scan_all_interval * SECS_PER_DAY * HZ));
		ubi_msg(ubi, "finish bit flip check on all pebs, go to sleep for %d days.", ubi->bfcheck_scan_all_interval);
	}
}

void start_bfcheck(struct ubi_device *ubi, struct bfcheck_params *bfcheck_params)
{
	ubi->bfcheck_cur_peb = 0;
	ubi->bfcheck_should_work = false;
	ubi->bfcheck_scan_all_interval = bfcheck_params->scan_all_interval;
	ubi->bfcheck_scan_block_interval = bfcheck_params->scan_block_interval;

	if (ubi->bfcheck_scan_all_interval != 0 && ubi->bfcheck_scan_block_interval != 0) {
		ubi->bfcheck_should_work = true;

		INIT_DELAYED_WORK(&ubi->bfcheck_work, ubi_bfcheck_traverse_peb);
		schedule_delayed_work(&ubi->bfcheck_work, ubi->bfcheck_scan_all_interval * SECS_PER_DAY * HZ);

		ubi_msg(ubi, "backgound bit flip activate. scan all interval:%d scan block interval:%d",
			ubi->bfcheck_scan_all_interval, ubi->bfcheck_scan_block_interval);
	}
}

int check_bfcheck_params(struct bfcheck_params *bfcheck_params)
{
	if (bfcheck_params->scan_all_interval < 0 ||
		bfcheck_params->scan_block_interval < 0 ||
		bfcheck_params->scan_all_interval > UBI_MAX_SCAN_ALL_INTERVAL ||
		bfcheck_params->scan_block_interval > UBI_MAX_SCAN_BLOCK_INTERVAL)
		return -EINVAL;
	return 0;
}

void stop_bfcheck(struct ubi_device *ubi)
{
	if (ubi->bfcheck_should_work) {
		ubi->bfcheck_should_work = false;
		cancel_delayed_work_sync(&ubi->bfcheck_work);
	}
}

int parse_bfcheck_params(char *scan_all_interval, char *scan_block_interval, struct bfcheck_params *bfcheck_params)
{
	bfcheck_params->scan_all_interval = 0;
	bfcheck_params->scan_block_interval = 0;

	if (scan_all_interval) {
		int interval = 0;
		int err = kstrtoint(scan_all_interval, 10, &interval);
		if (err || interval < 0 || interval > UBI_MAX_SCAN_ALL_INTERVAL) {
			pr_err("UBI error: bad value:%s for interval of all block scan\n", scan_all_interval);
			return -EINVAL;
		}
		bfcheck_params->scan_all_interval = interval;
	}

	if (scan_block_interval) {
		int interval = 0;
		int err = kstrtoint(scan_block_interval, 10, &interval);
		if (err || interval < 0 || interval > UBI_MAX_SCAN_BLOCK_INTERVAL) {
			pr_err("UBI error: bad value:%s for interval of block scan\n", scan_block_interval);
			return -EINVAL;
		}
		bfcheck_params->scan_block_interval = interval;
	}

	return 0;
}
